import os, sys
sys.path[0:0] = ['framework']

import datetime
#from datetime import datetime, timedelta
from collections import defaultdict
import logging
import bottle
from bottle import request, response
from month import MonthList, OrReplyKey, YearMonth
from datamodel import Account, Article, Reply

from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import ndb
from google.appengine.api import users
from google.appengine.api import mail

# [(u'sa', [3,2]), (u'gill', [1,1]), ...] the list is by time
def TagCategoryList(qry_all, qry_cur, is_tags):
    dict_list = defaultdict(list)
    # all
    articles = qry_all.fetch()
    dict_int = defaultdict(int)
    for a in articles:
        for i in (a.tags if is_tags else a.categories):
            dict_int[i] += 1
    for k, v in dict_int.items():
        dict_list[k].append(v)
        dict_list[k].append(0)
    # cur
    articles = qry_cur.fetch()
    dict_int = defaultdict(int)
    for a in articles:
        for i in (a.tags if is_tags else a.categories):
            dict_int[i] += 1
    for k, v in dict_int.items():
        dict_list[k][1] = v

    return dict_list.items()

def BuildSidebar():
    user = users.get_current_user()
    account = None
    if user and user.email():
        account = Account.get_by_id(user.email())

    purview = request.cookies.get('purview')
    language = request.cookies.get('language')

    qry = qry_all = Article.query() # order is asc
    if account:
        if purview in account.purviews:
            qry = qry.filter(Article.purview == purview)
        else:
            qry = qry.filter(Article.purview.IN(account.purviews))
    else:
        qry = qry.filter(Article.purview == 'anonymous')

    # get popular posts and recent comments
    popular_posts = qry.order(-Article.counter).fetch(5)
    recent_comments_key = []
    for reply_key in Reply().query(Reply.approval == True).order(-Reply.when).iter(keys_only=True):
        if reply_key.parent().get().purview in (account.purviews if account else ['anonymous']):
            recent_comments_key.append(reply_key)
            if len(recent_comments_key) >= 5:
                break
    if language:
        qry = qry.filter(Article.language == language)

    # filter by purview & language
    tag_list = TagCategoryList(qry_all, qry, is_tags=True)
    category_list = TagCategoryList(qry_all, qry, is_tags=False)

    return {'account':account,'tags':tag_list,'categories':category_list,
            'popular_posts':popular_posts, 'recent_comments_key':recent_comments_key}

def ErrorPage(status, title):
    response.status = status
    return bottle.template('templates/message', sidebar=BuildSidebar(), title=title, message=None, redirect=None)

@bottle.route('/id/<id_val>')
def ReadArticle(id_val=None):
    user = users.get_current_user()
    account = None
    if user and user.email():
        account = Account.get_by_id(user.email())

    date = request.GET.get('date') # '2013.5' or '2013.5~2013.3'

    if date is not None:
        date_new = date.split('~')[0].split('.') # WARN: not int
        date_old = date_new = int(date_new[0]), int(date_new[1])
        if len(date.split('~')) == 2:
            date_old = date.split('~')[1].split('.') # WARN: not int
            date_old = int(date_old[0]), int(date_old[1]), False # make sure not equal to data_new
    else:
        date = datetime.date.today()
        date_old = date_new = date.year, date.month

    # show page
    article = Article.get_by_id(int(id_val))
    if article is None:
        return ErrorPage(404, 'the article not exist.')
    if account is None:
        if article.purview != 'anonymous':
            return ErrorPage(403, 'out of your purviews, you are logged out.')
    elif not article.purview in account.purviews:
        return ErrorPage(403, 'out of your purviews.')

    reply_qry = Reply.query(ancestor=article.key)
    month_list = MonthList(Reply, reply_qry, date_new, date_old, article.key.id())
    replies = None
    if month_list:
        cur = [elem for elem in month_list if elem[2]][0]
        replies = reply_qry.filter(Reply.key > OrReplyKey(Reply, cur[1], article.key.id()))\
            .filter(Reply.key < OrReplyKey(Reply, YearMonth(cur[0], + 1), article.key.id())).fetch()

    if request.cookies.get(id_val) is None:
        article.counter += 1;
        article.put()
    response.set_cookie(id_val, '1', max_age='3600') # 1 hour
    return bottle.template('templates/article_show', sidebar=BuildSidebar(),
                           article=article, month_list=month_list, replies=replies)

@bottle.route('/')
@bottle.route('/category/<category>')
@bottle.route('/tag/<tag>')
def ListArticle(category=None, tag=None):
    user = users.get_current_user()
    account = None
    if user and user.email():
        account = Account.get_by_id(user.email())

    purview = request.cookies.get('purview')
    language = request.cookies.get('language')
    date = request.GET.get('date') # '2013.5' or '2013.5~2013.3'

    qry_all = qry = Article.query().order(Article.key) # order is asc
    if account:
        if purview in account.purviews:
            qry = qry.filter(Article.purview == purview)
        else:
            qry = qry.filter(Article.purview.IN(account.purviews))
    else:
        qry = qry.filter(Article.purview == 'anonymous')

    if language:
        qry = qry.filter(Article.language == language)

    if date is not None:
        date_new = date.split('~')[0].split('.') # WARN: not int
        date_old = date_new = int(date_new[0]), int(date_new[1])
        if len(date.split('~')) == 2:
            date_old = date.split('~')[1].split('.') # WARN: not int
            date_old = int(date_old[0]), int(date_old[1]), False # make sure not equal to data_new
    else:
        date = datetime.date.today()
        date_old = date_new = date.year, date.month

    # show list
    if category is not None:
        qry = qry.filter(Article.categories == category)
    elif tag is not None:
        qry = qry.filter(Article.tags == tag)

    month_list = MonthList(Article, qry, date_new, date_old)
    if not month_list:
        return ErrorPage(404, 'Found 0 page...')
    cur = [elem for elem in month_list if elem[2]][0]
    articles = qry.filter(Article.key > OrReplyKey(Article, cur[1]))\
        .filter(Article.key < OrReplyKey(Article, YearMonth(cur[0], + 1))).fetch()

    return bottle.template('templates/article_list', sidebar=BuildSidebar(),
                           articles=reversed(articles), month_list=month_list)

@bottle.route('/id/<id_val>', method='POST')
def PostComment(id_val):
    user = users.get_current_user()
    if user and user.email():
        account = Account.get_by_id(user.email())
    else:
        guest_name = request.POST['name'].strip();
        guest_site = request.POST['site'].strip();
        guest_email = request.POST['email'];
        if not (guest_name and guest_email):
            return ErrorPage(412, 'name or email is empty!')
        if not mail.is_email_valid(guest_email):
            return ErrorPage(412, 'email address not valid!')
        import random, string
        while True:
            guest_email = guest_email + "*" + ''.join(random.choice(string.ascii_uppercase + string.lowercase + string.digits) for x in range(8))
            if Account.get_by_id(guest_email) is None:
                break
        account = Account(id=guest_email, nickname=guest_name, site=guest_site)
        account.put()

    comment = request.POST['comment']
    if not comment:
        return ErrorPage(412, 'your comment is empty!')

    article = Article.get_by_id(int(id_val))
    if article is None:
        return ErrorPage(412, 'the article not exist.')

    when = long(datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3])
    reply = Reply(id = when, when = when,
                  parent = article.key, author_key = account.key, body = comment)
    reply.put()
    if len(account.purviews) == 0: # guest
        return bottle.template('templates/message', sidebar=BuildSidebar(), title="Successed.", message="""
        Require approval for comments before they're posted,
        you can't see the pending comments when you don't use OpenID.""", redirect=request.url)
    bottle.redirect(request.url)

# login

@bottle.route('/account', method='POST')
def AccountUpdate():
    user = users.get_current_user()
    if user and user.email():
        account = Account.get_by_id(user.email())
    else:
        return ErrorPage(403, 'need login.')

    nickname = request.POST['nickname']
    if not nickname:
        return ErrorPage(412, 'please input your nickname!')
    #if account.nickname != nickname: # change name
    #    if Account.query(Account.nickname == nickname).count():
    #        return ErrorPage(412, 'sorry, the name "%s" has already been taken.'%nickname)

    account.nickname = nickname
    account.site = request.POST['site']
    account.message = request.POST['message']
    continue_url = request.POST['continue']
    account.put()
    bottle.redirect(continue_url)


@bottle.route('/account')
def AccountSetting():
    continue_url = request.GET.get('continue')

    user = users.get_current_user()
    if not user:
        return ErrorPage(403, 'Please login first!')
    if not mail.is_email_valid(user.email()):
        return ErrorPage(412, "Your OpenID provider hide your email address, "
                         "please check the setting, or login with another provider.")
    account = Account.get_or_insert(user.email(),
                                    nickname = user.email().split('@')[0],
                                    purviews = ['anonymous'])

    return bottle.template('templates/account', sidebar=BuildSidebar(), continue_url=continue_url)

@bottle.route('/login')
@bottle.route('/_ah/login_required')
def LoginHandle():
    continue_url = request.GET.get('continue')

    openid_url = request.GET.get('openid')
    if not openid_url:
        return bottle.template('templates/login', continue_url=continue_url)
    else:
        continue_url = '/account?continue=' + (continue_url or '/')
        bottle.redirect(users.create_login_url(continue_url, None, openid_url))

# admin

@bottle.route('/admin/approval', method='POST')
def ApprovalReply():
    if not users.is_current_user_admin():
        return ErrorPage(403, 'You are not admin!')
    article_id = int(request.POST['article'])
    reply_id = int(request.POST['reply'])
    approval = request.POST['approval']
    reply = ndb.Key(Article, article_id, Reply, reply_id).get()
    if approval == 'approve':
        reply.approval = True
        reply.put()
    if approval == 'delete':
        account = Account.get_by_id(reply.author_key.id())
        if len(account.purviews) == 0: # guest
            account.key.delete()
        reply.key.delete()
    if approval == 'edit':
        return bottle.template('templates/edit_comment', sidebar=BuildSidebar(), reply=reply)
    bottle.redirect('/id/' + request.POST['article'])

@bottle.route('/admin/edit_comment', method='POST')
def UpdateComment():
    if not users.is_current_user_admin():
        return ErrorPage(403, 'You are not admin!')
    article_id = int(request.POST['article'])
    reply_id = int(request.POST['reply'])
    reply = ndb.Key(Article, article_id, Reply, reply_id).get()
    if not reply:
        return ErrorPage(412, 'reply not found!')
    reply.body = request.POST['body']
    reply.put()
    bottle.redirect('/id/' + request.POST['article'])
#

@bottle.route('/admin/edit_article')
@bottle.route('/admin/edit_article/<id_val>')
def EditArticleGet(id_val=None):
    if not users.is_current_user_admin():
        return ErrorPage(403, 'You are not admin!')
    article = Article.get_by_id(int(id_val)) if id_val else None
    return bottle.template('templates/edit_article', sidebar=BuildSidebar(), article=article)

@bottle.route('/admin/post_article', method='POST')
def EditArticlePost():
    if not users.is_current_user_admin():
        return ErrorPage(403, 'You are not admin!')

    action = request.POST['action']
    if action == 'new' or action == 'modify':
        if action == 'new':
            article = Article(id = long(datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3]))
        else:
            article = Article.get_by_id(int(request.POST['id']))

        article.title = request.POST['title'].strip()
        article.language = request.POST['language'].strip()
        article.summary = request.POST['summary']
        article.body = request.POST['body']
        article.categories = request.POST['categories'].strip().split(',')
        article.tags = request.POST['tags'].strip().split(',')
        article.purview = request.POST['purview'].strip()
        article.put()

    elif action == 'delete':
        article = Article.get_by_id(int(request.POST['id']))
        replies = Reply.query(ancestor=article.key)
        for reply in replies:
            reply.key.delete()
        article.key.delete()

    bottle.redirect('/')
#

@bottle.route('/admin/edit_account/<email>')
def EditAccount(email):
    if not users.is_current_user_admin():
        return ErrorPage(403, 'You are not admin!')
    account = Account.get_by_id(email)
    if not account:
        return ErrorPage(412, 'user not found by email: %s'%email)
    return bottle.template('templates/edit_account', sidebar=BuildSidebar(), who=account)

@bottle.route('/admin/edit_account', method='POST')
def UpdateAccount():
    if not users.is_current_user_admin():
        return ErrorPage(403, 'You are not admin!')
    email = request.POST['email']
    account = Account.get_by_id(email)
    if not account:
        return ErrorPage(412, 'user not found by email: %s'%email)
    if request.POST['purviews']:
        account.purviews = request.POST['purviews'].split(',')
    account.note = request.POST['note']
    account.put()
    bottle.redirect('/')
#

@bottle.route('/admin')
def AdminIndex():
    if not users.is_current_user_admin():
        return ErrorPage(403, 'You are not admin!')

    replies_key = Reply.query(Reply.approval==False).fetch(keys_only=True)
    dict_new_replies = defaultdict(int)
    for i in replies_key:
        dict_new_replies[i.parent().id()] += 1

    return bottle.template('templates/admin_index', sidebar=BuildSidebar(), dict_new_replies=dict_new_replies)

app = bottle.app()
bottle.run(app=app, server='gae', debug=False)

